/**
 * @file shell.c
 * @author zhaitao (zhaitao.as@outlook.com)
 * @brief export shell command
 * @version 0.1
 * @date 2020-07-10
 * 
 * @copyright Copyright (c) 2020
 * 
 */

#include "stdio.h"
#include "unistd.h"
#include <stdlib.h>
#include <string.h>

#include "app.h"
#include "main.h"
#include "shell_export.h"
#include "util.h"

ntshell_t ntshell;
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern uint8_t app_debug;

typedef int (*USRCMDFUNC)(int argc, char **argv);

typedef struct {
	char *cmd;
	char *desc;
	USRCMDFUNC func;
} cmd_table_t;

static const cmd_table_t cmdlist[];

int uart_putc(char c)
{
	HAL_UART_Transmit(&huart2, (uint8_t *)&c, 1, 0xffff);
	return c;
}

int uart_getc(void)
{
	int rcv;
	HAL_UART_Receive(&huart2, (uint8_t *)&rcv, 1, 0xffff);
	return rcv;
}

int uart_puts(const char *s)
{
	int cnt = 0;
	char *p = (char *)s;
	while (*p) {
		uart_putc(*p);
		p++;
		cnt++;
	}
	return cnt;
}

#define UNUSED_VARIABLE(N) \
	do {               \
		(void)(N); \
	} while (0)

int func_read(char *buf, int cnt, void *extobj)
{
	int i;
	UNUSED_VARIABLE(extobj);
	for (i = 0; i < cnt; i++) {
		buf[i] = uart_getc();
	}
	return cnt;
}

int func_write(const char *buf, int cnt, void *extobj)
{
	int i;
	UNUSED_VARIABLE(extobj);
	for (i = 0; i < cnt; i++) {
		uart_putc(buf[i]);
	}
	return cnt;
}

static int ntshell_help(int argc, char **argv);
static int ntshell_info(int argc, char **argv);
static int ntshell_bl(int argc, char **argv);
static int ntshell_pwm(int argc, char **argv);
static int ntshell_detect(int argc, char **argv);
static int ntshell_debug(int argc, char **argv);

static const cmd_table_t cmdlist[] = {
    {"help", "This is a description text string for help command.", ntshell_help},
    {"info", "This is a description text string for info command.", ntshell_info},
    {"bl", "Control Backlight", ntshell_bl},
    {"pwm", "Control PWM", ntshell_pwm},
    {"detect", "detect minipd is online", ntshell_detect},
    {"debug", "turn on/off debug output", ntshell_debug},
};

static int usrcmd_ntopt_callback(int argc, char **argv, void *extobj)
{
	if (argc == 0) {
		return 0;
	}
	const cmd_table_t *p = &cmdlist[0];
	for (int i = 0; i < sizeof(cmdlist) / sizeof(cmdlist[0]); i++) {
		if (ntlibc_strcmp((const char *)argv[0], p->cmd) == 0) {
			return p->func(argc, argv);
		}
		p++;
	}
	uart_puts("Unknown command found.\r\n");
	return 0;
}

int usrcmd_execute(const char *text)
{
	return ntopt_parse(text, usrcmd_ntopt_callback, 0);
}

int func_callback(const char *text, void *extobj)
{
	usrcmd_execute(text);
	return 0;
}

static int ntshell_help(int argc, char **argv)
{
	const cmd_table_t *p = &cmdlist[0];
	for (int i = 1; i < sizeof(cmdlist) / sizeof(cmdlist[0]); i++) {
		uart_puts(cmdlist[i].cmd);
		uart_puts("\t:");
		uart_puts(cmdlist[i].desc);
		uart_puts("\r\n");
		p++;
	}
	return 0;
}

static int ntshell_info(int argc, char **argv)
{
	if (argc != 2) {
		uart_puts("info sys\r\n");
		uart_puts("info ver\r\n");
		return 0;
	}
	if (ntlibc_strcmp(argv[1], "sys") == 0) {
		uart_puts("NXP LPC824 Monitor\r\n");
		return 0;
	}
	if (ntlibc_strcmp(argv[1], "ver") == 0) {
		uart_puts("Version 0.0.0\r\n");
		return 0;
	}
	uart_puts("Unknown sub command found\r\n");
	return -1;
}

int ntshell_bl(int argc, char **argv)
{
	const char help[] = "\
	-g\t\tget setting\r\n\
	-h\t\thelp\r\n\
	-i\t\tcurrent\r\n\
	-v\t\tvoltage\r\n";

	float cur;
	float vol;

	if (app_debug) {
		for (uint8_t i = 0; i < argc; i++) {
			app_dprintf("argv[%d] = %s\r\n", i, argv[i]);
		}
	}

	if (argc <= 1) {
		printf(help);
	}

	int option;
	optind = 0;

	while ((option = getopt(argc, argv, "-:g:hi:v:x")) != -1) { //get option from the getopt() method
		switch (option) {
		case 'g':
			app_dprintf("get setting: %s\n", optarg);
			break;
		case 'h':
			printf(help);
			break;
		case 'i':
			app_dprintf("set current: %s\n", optarg);
			cur = strtod(optarg, NULL);
			app_setCurrent(cur);
			break;
		case 'n':
			app_dprintf("output disable: %s\n", optarg);
			// TODO
			break;
		case 'v':
			app_dprintf("set voltage: %s\n", optarg);
			vol = strtod(optarg, NULL);
			app_setVoltage(vol);
			break;
		case 'y':
			app_dprintf("output enable: %s\n", optarg);
			// TODO
			break;
		case ':':
			app_dprintf("option needs a value\n");
			app_dprintf("current option: %c\n", optopt);
			if (optopt == 'g') {
				app_dprintf("return all settings\r\n");
				vol = app_getVoltage();
				cur = app_getCurrent();
				printf("V=%3.3f I=%3.3f \r\n", vol, cur);
			}
			break;
		case '?': //used for some unknown options
			app_dprintf("unknown option: %c\n", optopt);
			break;
		case 1:
			app_dprintf("Non-option arg: %s\n", optarg);
			break;
		}
	}
	for (; optind < argc; optind++) { //when some extra arguments are passed
		app_dprintf("Given extra arguments: %s\n", argv[optind]);
	}
	return 0;
}

static int ntshell_pwm(int argc, char **argv)
{
	if (app_debug) {
		for (uint8_t i = 0; i < argc; i++) {
			app_dprintf("argv[%d] = %s\r\n", i, argv[i]);
		}
	}

	int option;
	optind = 0;

	while ((option = getopt(argc, argv, "-:d:f:g:x")) != -1) { //get option from the getopt() method
		switch (option) {
		case 'd':
			app_dprintf("set duty: %s\n", optarg);
			break;
		case 'f':
			app_dprintf("set frequence: %s\n", optarg);
			break;
		case 'g':
			app_dprintf("get setting: %s\n", optarg);
			break;
		case ':':
			app_dprintf("option needs a value\n");
			app_dprintf("current option: %c\n", optopt);
			if (optopt == 'g') {
				app_dprintf("return all settings\r\n");
			}
			break;
		case '?': //used for some unknown options
			app_dprintf("unknown option: %c\n", optopt);
			break;
		case 1:
			app_dprintf("Non-option arg: %s\n", optarg);
			break;
		}
	}
	for (; optind < argc; optind++) { //when some extra arguments are passed
		app_dprintf("Given extra arguments: %s\n", argv[optind]);
	}
	return 0;
}

static int ntshell_detect(int argc, char **argv)
{
	app_dprintf("MINIPD\r\n");
	return 0;
}

static int ntshell_debug(int argc, char **argv)
{
	if (app_debug) {
		for (uint8_t i = 0; i < argc; i++) {
			app_dprintf("argv[%d] = %s\r\n", i, argv[i]);
		}
	}

	int option;
	optind = 1;

	while ((option = getopt(argc, argv, "-:ynYNx")) != -1) { //get option from the getopt() method
		switch (option) {
		case 'y':
		case 'Y':
			app_dprintf("Enable debug output\n");
			app_debug = 1;
			break;
		case 'n':
		case 'N':
			app_dprintf("Disable debug output\n");
			app_debug = 0;
			break;
		case ':':
			app_dprintf("option needs a value\n");
			app_dprintf("current option: %c\n", optopt);
			if (optopt == 'g') {
				app_dprintf("return all settings\r\n");
			}
			break;
		case '?': //used for some unknown options
			app_dprintf("unknown option: %c\n", optopt);
			break;
		case 1:
			app_dprintf("Non-option arg: %s\n", optarg);
			break;
		}
	}
	for (; optind < argc; optind++) { //when some extra arguments are passed
		app_dprintf("Given extra arguments: %s\n", argv[optind]);
	}
	return 0;
}